<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>18.4.1-客户端激活的原理</title>
</head>
<body>
    <div id="app"></div>
</body>
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        /**
         * 什么是客户端激活呢？
         * - 对于同构渲染来说，组件的代码会在服务端和客户端分别执行一次
         * - 在服务端，组件会被渲染为静态的 HTML 字符串，
         *    然后发送给浏览器，浏览器再把这段纯静态的HTML 渲染出来
         * 
         * 组件代码在客户端运行时，仍然需要做两件重要的事
         * - 在页面中的 DOM 元素与虚拟节点对象之间建立联系；
         * - 为页面中的 DOM 元素添加事件绑定。
         * 
         * 真实 DOM 元素与虚拟 DOM 对象都是树型结构，并且节点之间存在一一对应的关系
         * - 我们可以认为它们是“同构”的
         * - 激活的原理就是基于这一事实，递归地在真实 DOM 元素与虚拟 DOM 节点之间建立关系
         * - 在虚拟 DOM 中并不存在与容器元素（或挂载点）对应的节点。
         *    因此，在激活的时候，应该从容器元素的第一个子节点开始
         * 
         * hydrateElement 函数有两个关键点
         * - 因为服务端渲染是忽略事件的，浏览器只是渲染了静态的 HTML 而已，
         *    所以激活 DOM 元素的操作之一就是为其添加事件处理程序。
         * - 递归地激活当前元素的子节点，
         *    从第一个子节点 el.firstChild 开始，递归地调用 hydrateNode 函数完成激活
         * 
         * 由于服务端渲染的页面中已经存在真实 DOM 元素，
         *  所以当调用 mountComponent 函数进行组件的挂载时，无须再次创建真实DOM 元素
        */


        /**
         * 
         *  
         * 
         * */ 

        const {  shallowReadonly, shallowReactive, effect, ref } = VueReactivity;
        

        // 应该忽略的属性
        const shouldIgnoreProp = ['key', 'ref']

        function renderAttrs(props) {
            let ret = ''
            for (const key in props) {
                if (
                    // 检测属性名称，如果是事件或应该被忽略的属性，则忽略它
                    shouldIgnoreProp.includes(key) ||
                    /^on[^a-z]/.test(key)
                ) {
                    continue
                }
                const value = props[key]
                // 调用 renderDynamicAttr 完成属性的渲染
                ret += renderDynamicAttr(key, value)
            }
            return ret
        }

        // 用来判断属性是否是 boolean attribute
        const isBooleanAttr = (key) =>
            (`itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly` +
            `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` +
            `loop,open,required,reversed,scoped,seamless,` +
            `checked,muted,multiple,selected`).split(',').includes(key)

        // 用来判断属性名称是否合法且安全
        const isSSRSafeAttrName = (key) => !/[>/="'\u0009\u000a\u000c\u0020]/.test(key)

        function renderDynamicAttr(key, value) {
            if (isBooleanAttr(key)) {
                // 对于 boolean attribute，如果值为 false，则什么都不需要渲染，否则只需要渲染 key 即可
                return value === false ? `` : ` ${key}`
            } else if (isSSRSafeAttrName(key)) {
                // 对于其他安全的属性，执行完整的渲染，
                // 注意：对于属性值，我们需要对它执行 HTML 转义操作
                return value === '' ? ` ${key}` : ` ${key}="${escapeHtml(value)}"`
            } else {
                // 跳过不安全的属性，并打印警告信息
                console.warn(
                    `[@vue/server-renderer] Skipped rendering unsafe attribute name: ${key}`
                )
                return ``
            }
        }

        const escapeRE = /["'&<>]/ 
        function escapeHtml(string) {
            const str = '' + string
            const match = escapeRE.exec(str)

            if (!match) {
                return str
            }

            let html = ''
            let escaped
            let index
            let lastIndex = 0
            for (index = match.index; index < str.length; index++) {
                switch (str.charCodeAt(index)) {
                    case 34: // "
                        escaped = '&quot;'
                        break
                    case 38: // &
                        escaped = '&amp;'
                        break
                    case 39: // '
                        escaped = '&#39;'
                        break
                    case 60: // <
                        escaped = '&lt;'
                        break
                    case 62: // >
                        escaped = '&gt;'
                        break
                    default:
                        continue
                }

                if (lastIndex !== index) {
                    html += str.substring(lastIndex, index)
                }

                lastIndex = index + 1
                html += escaped
            }

            return lastIndex !== index ? html + str.substring(lastIndex, index) : html
        }

        const VOID_TAGS = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'.split(',')
        // 在不考虑任何边界条件的情况下，实现 renderElementVNode
        function renderElementVNode(vnode){
            // 取出标签名称 tag 和标签属性 props，以及标签的子节点
            const { type: tag, props, children } = vnode
            // 判断是否是 void element
            const isVoidElement = VOID_TAGS.includes(tag)
            // 开始标签的头部
            let ret = `<${tag}`
            // 处理标签属性
            if (props) {
                // 调用 renderAttrs 函数进行严谨处理
                ret += renderAttrs(props)
            }

            // 如果是 void element，则自闭合
            ret += isVoidElement ? `/>` : `>` 
            // 如果是 void element，则直接返回结果，无须处理 children，因为 void element 没有 children
            if (isVoidElement) return ret 

            // 处理子节点
            // 如果子节点的类型是字符串，则是文本内容，直接拼接
            if (typeof children === 'string') {
                ret += children
            } else if (Array.isArray(children)) {
                // 如果子节点的类型是数组，则递归地调用 renderElementVNode 完成渲染
                children.forEach(child => {
                    ret += renderElementVNode(child)
                })
            }

            // 结束标签
            ret += `</${tag}>`

            // 返回拼接好的 HTML 字符串
            return ret
        }

        // function renderComponentVNode(vnode) {
        //     // 获取 setup 组件选项
        //     let { type: { setup } } = vnode
        //     // 执行 setup 函数得到渲染函数 render
        //     const render = setup()
        //     // 执行渲染函数得到 subTree，即组件要渲染的内容
        //     const subTree = render()
        //     // 调用 renderVNode 完成渲染，并返回其结果
        //     return renderVNode(subTree)
        // }

        function renderComponentVNode(vnode) {
            const isFunctional = typeof vnode.type === 'function'
            let componentOptions = vnode.type
            if (isFunctional) {
                componentOptions = {
                    render: vnode.type,
                    props: vnode.type.props
                }
            }
            let { render, data, setup, beforeCreate, created, props: propsOption } = componentOptions

            beforeCreate && beforeCreate()

            // 无须使用 reactive() 创建 data 的响应式版本
            const state = data ? data() : null
            const [props, attrs] = resolveProps(propsOption, vnode.props)

            const slots = vnode.children || {}

            const instance = {
                state,
                props, // props 无须 shallowReactive
                isMounted: false,
                subTree: null,
                slots,
                mounted: [],
                keepAliveCtx: null
            }

            function emit(event, ...payload) {
                const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
                const handler = instance.props[eventName]
                if (handler) {
                    handler(...payload)
                } else {
                    console.error('事件不存在')
                }
            }

            // setup
            let setupState = null
            if (setup) {
                const setupContext = { attrs, emit, slots }
                const prevInstance = setCurrentInstance(instance)
                const setupResult = setup(shallowReadonly(instance.props), setupContext)
                setCurrentInstance(prevInstance)
                if (typeof setupResult === 'function') {
                    if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
                    render = setupResult
                } else {
                    setupState = setupContext
                }
            }

            vnode.component = instance

            const renderContext = new Proxy(instance, {
                get(t, k, r) {
                    const { state, props, slots } = t

                    if (k === '$slots') return slots

                    if (state && k in state) {
                        return state[k]
                    } else if (k in props) {
                        return props[k]
                    } else if (setupState && k in setupState) {
                        return setupState[k]
                    } else {
                        console.error('不存在')
                    }
                },
                set (t, k, v, r) {
                    const { state, props } = t
                    if (state && k in state) {
                        state[k] = v
                    } else if (k in props) {
                        props[k] = v
                    } else if (setupState && k in setupState) {
                        setupState[k] = v
                    } else {
                        console.error('不存在')
                    }
                }
            })

            created && created.call(renderContext)

            const subTree = render.call(renderContext, renderContext)

            return renderVNode(subTree)
        }

        /**
         * resolveProps 函数用于解析组件 props 和 attrs 数据
         * @param {Object} options 组件的 props 选项
         * @param {Object} propsData 为组件传递的 props 数据
         * */ 
        function resolveProps(options = {}, propsData = {}) {
            const props = {}
            const attrs = {}
            // 遍历为组件传递的 props 数据
            for (const key in propsData) {
                // 以字符串 on 开头的 props，无论是否显式地声明，
                // 都将其添加到 props 数据中，而不是添加到 attrs 中
                if (key in options || key.startsWith('on')) {
                    // 如果为组件传递的 props 数据在组件自身的 props 选项中有定义，则将其视为合法的 props
                    props[key] = propsData[key]
                } else {
                    // 否则将其作为 attrs
                    attrs[key] = propsData[key]
                }
            }
            // 最后返回 props 与 attrs 数据
            return [ props, attrs ]
        }

        // 全局变量，存储当前正在被初始化的组件实例
        let currentInstance = null
        // 该方法接收组件实例作为参数，并将该实例设置为 currentInstance
        function setCurrentInstance(instance) {
            currentInstance = instance
        }

        function renderVNode(vnode) {
            const type = typeof vnode.type
            if (type === 'string') {
                return renderElementVNode(vnode)
            } else if (type === 'object' || type === 'function') {
                return renderComponentVNode(vnode)
            } else if (vnode.type === Text) {
                // 处理文本...
            } else if (vnode.type === Fragment) {
                // 处理片段...
            } else {
                // 其他 VNode 类型
            }
        }
        

        function createRenderer(options) {
            // 通过 options 得到操作 DOM 的 API
            const {
                setElementText,
                patchProps,
            } = options
            function hydrate(vnode, container) { // 新增 hydrate
                // 从容器元素的第一个子节点开始
                hydrateNode(container.firstChild, vnode)
            }

            function hydrateNode(node, vnode) { // 新增 hydrateNode
                const { type } = vnode
                // 1. 让 vnode.el 引用真实 DOM
                vnode.el = node

                // 2. 检查虚拟 DOM 的类型，如果是组件，则调用 mountComponent 函数完成激活
                if (typeof type === 'object') {
                    mountComponent(vnode, container, null)
                } else if (typeof type === 'string') {
                    // 3. 检查真实 DOM 的类型与虚拟 DOM 的类型是否匹配
                    if (node.nodeType !== 1) {
                        console.error('mismatch')
                        console.error('服务端渲染的真实 DOM 节点是：', node)
                        console.error('客户端渲染的虚拟 DOM 节点是：', vnode)
                    } else {
                        // 4. 如果是普通元素，则调用 hydrateElement 完成激活
                        console.log('4. 如果是普通元素，则调用 hydrateElement 完成激活')
                        hydrateElement(node, vnode)
                    }
                }

                // 5. 重要：hydrateNode 函数需要返回当前节点的下一个兄弟节点，以便继续进行后续的激活操作
                return node.nextSibling
            }

            // 用来激活普通元素类型的节点
            function hydrateElement(el, vnode) { // 新增 hydrateElement
                // 1. 为 DOM 元素添加事件
                if (vnode.props) {
                    for (const key in vnode.props) {
                        // 只有事件类型的 props 需要处理
                        if (/^on/.test(key)) {
                            patchProps(el, key, null, vnode.props[key])
                        }
                    }
                }
                // 递归地激活子节点
                if (Array.isArray(vnode.children)) {
                    // 从第一个子节点开始
                    let nextNode = el.firstChild
                    const len = vnode.children.length
                    for (let i = 0; i < len; i++) {
                        // 激活子节点，注意，每当激活一个子节点，hydrateNode 函数都会返回当前子节点的下一个兄弟节点，
                        // 于是可以进行后续的激活了
                        nextNode = hydrateNode(nextNode, vnode.children[i])
                    }
                }
            }

            // 完成组件渲染
            function mountComponent(vnode, container, anchor) {
                // 检查是否是函数式组件
                const isFunctional = typeof vnode.type === 'function'

                // 通过 vnode 获取组件的选项对象，即 vnode.type
                let componentOptions = vnode?.type || {}

                if (isFunctional){
                    // 如果是函数式组件，则将 vnode.type 作为渲染函数，将 vnode.type.props 作为 props 选项定义即可
                    componentOptions = {
                        render: vnode.type,
                        props: vnode.props
                    }
                }

                // 获取组件的渲染函数 render, 以及 data 函数
                // 从组件选项对象中取得组件的生命周期函数
                let { 
                        render, data, 
                        props: propsOption,
                        beforeCreate, created, 
                        beforeMount, mounted, 
                        beforeUpdate, updated,
                        setup
                    } = componentOptions
                // 在这里调用 beforeCreate 钩子
                beforeCreate && beforeCreate()
                // 调用 data 函数得到原始数据，并调用 reactive 函数将其包装为响应式数据
                const state = data ? reactive(data()) : null
                // 调用 resolveProps 函数解析出最终的 props 数据与 attrs 数据
                const [props, attrs] = resolveProps(propsOption, vnode.props)
                // 定义组件实例，一个组件实例本质上就是一个对象，它包含与组件有关的状态信息
                // 直接使用编译好的 vnode.children 对象作为 slots 对象即可
                const slots = vnode.children || {}
                const instance = {
                    // 组件自身的状态数据，即 data
                    state,
                    // 将解析出的 props 数据包装为 shallowReactive 并定义到组件实例上
                    props: shallowReactive(props),
                    // 一个布尔值，用来表示组件是否已经被挂载，初始值为 false
                    isMounted: false,
                    // 组件所渲染的内容，即子树（subTree）
                    subTree: null,
                    slots,
                    // 在组件实例中添加 mounted 数组，用来存储通过 onMounted 函数注册的生命周期钩子函数
                    mounted: [],
                    // 在组件实例中添加 unmounted 数组，用来存储通过 onUnmounted 函数注册的生命周期钩子函数
                    unmounted: []
                }

                /**
                 * 定义 emit 函数，用于发射自定义事件
                 * @param {string} event 事件名称
                 * @param  {...any} payload 传递给事件处理函数的参数
                 * */ 
                function emit(event, ...payload) {
                    // 根据约定对事件名称进行处理，例如 change --> onChange
                    const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
                    // 根据处理后的事件名称去 props 中寻找对应的事件处理函数
                    const handler = instance.props[eventName]
                    if (handler) {
                        // 调用事件处理函数并传递参数
                        handler(...payload)
                    } else {
                        console.error('事件不存在')
                    }
                }

                
                
                // 将 emit 函数添加到 setupContext 中，用户可以通过 setupContext 取得 emit 函数
                // 将 slots 对象添加到 setupContext 中
                const setupContext = { attrs, emit, slots }

                // 在调用 setup 函数之前，设置当前组件实例
                setCurrentInstance(instance)

                // 调用 setup 函数，将只读版本的 props 作为第一个参数传递，避免用户意外地修改 props 的值，
                // 将 setupContext 作为第二个参数传递
                const setupResult = setup&&setup(shallowReadonly(instance.props), setupContext)

                // 在 setup 函数执行完毕之后，重置当前组件实例
                setCurrentInstance(null)

                // setupState 用来存储由 setup 返回的数据
                let setupState = null
                // 如果 setup 函数的返回值是函数，则将其作为渲染函数
                if (typeof setupResult === 'function') {
                    // 报告冲突
                    if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
                    // 将 setupResult 作为渲染函数
                    render = setupResult
                } else {
                    // 如果 setup 的返回值不是函数，则作为数据状态赋值给 setupState
                    setupState = setupResult
                }

                // 将组件实例设置到 vnode 上，用于后续更新
                vnode.component = instance

                // 创建渲染上下文对象，本质上是组件实例的代理
                const renderContext = new Proxy(instance, {
                    get(t, k, r) {
                        // 取得组件自身状态与 props 数据
                        const { state, props, slots } = t 
                        // 当 k 的值为 $slots 时，直接返回组件实例上的 slots
                        if (k === '$slots') return slots;
                        // 先尝试读取自身状态数据
                        if (state && k in state) {
                            return state[k]
                        } else if (k in props) { // 如果组件自身没有该数据，则尝试从 props 中读取
                            return props[k]
                        }else if(setupState && k in setupState){
                            // 渲染上下文需要增加对 setupState 的支持
                            return setupState[k]
                        } else {
                            console.error('不存在')
                        }
                    },
                    set (t, k, v, r) {
                        const { state, props } = t
                        if (state && k in state) {
                            state[k] = v
                        } else if (k in props) {
                            console.warn(`Attempting to mutate prop "${k}". Props are readonly.`)
                        } else if(setupState && k in setupState){
                            // 渲染上下文需要增加对 setupState 的支持
                            setupState[k] = v
                        } else {
                            console.error('不存在')
                        }
                    }
                })
                // 生命周期函数调用时要绑定渲染上下文对象

                // 在这里调用 created 钩子
                created && created.call(renderContext)
                
                // 将组件的 render 函数调用包装到 effect 内
                instance.update = effect(()=>{
                    // 调用 render 函数时，将其 this 设置为 state，
                    // 从而 render 函数内部可以通过 this 访问组件自身状态数据
                    // 执行渲染函数，获取组件要渲染的内容，即 render 函数返回的虚拟 DOM
                    const subTree = render&&render.call(renderContext, renderContext)
                    // 检查组件是否已经被挂载
                    if (!instance.isMounted) {
                        // 在这里调用 beforeMount 钩子
                        beforeMount && beforeMount.call(renderContext)
                        // 如果 vnode.el 存在，则意味着要执行激活
                        if (vnode.el) { // 新增
                            // 直接调用 hydrateNode 完成激活
                            hydrateNode(vnode.el, subTree)
                        } else {
                            // 正常挂载
                            // 初次挂载，调用 patch 函数第一个参数传递 null
                            patch(null, subTree, container, anchor)
                        }
                        // 重点：将组件实例的 isMounted 设置为 true，这样当更新发生时就不会再次进行挂载操作，
                        // 而是会执行更新
                        instance.isMounted = true
                        // 在这里调用 mounted 钩子
                        mounted && mounted.call(renderContext)

                        // 遍历 instance.mounted 数组并逐个执行即可
                        instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
                    } else {
                        // 在这里调用 beforeUpdate 钩子
                        beforeUpdate && beforeUpdate.call(renderContext)
                        // 当 isMounted 为 true 时，说明组件已经被挂载，只需要完成自更新即可，
                        // 所以在调用 patch 函数时，第一个参数为组件上一次渲染的子树，
                        // 意思是，使用新的子树与上一次渲染的子树进行打补丁操作
                        patch(instance.subTree, subTree, container, anchor)
                        // 在这里调用 updated 钩子
                        updated && updated.call(renderContext)
                    }
                    // 更新组件实例的子树
                    instance.subTree = subTree
                }, { 
                    // 指定该副作用函数的调度器为 queueJob 即可
                    scheduler: queueJob
                })

                

                
            }

            /**
             * 第一个参数 n1 代表旧 vnode，第二个参数 n2 代表新 vnode。
             * @param {VNode} n1 旧的 vnode
             * @param {VNode} n2 新的 vnode
             * @param {HTMLElement} container 容器
             * @param {HTMLElement} anchor 锚点元素
             * */ 
            function patch(n1, n2, container, anchor) {
                // 如果 n1 存在，则对比 n1 和 n2 的类型
                if (n1 && n1.type !== n2.type) {
                    // 如果新旧 vnode 的类型不同，则直接将旧 vnode 卸载
                    unmount(n1)
                    n1 = null
                }
                // 代码运行到这里，证明 n1 和 n2 所描述的内容相同
                const { type } = n2 || {};
                // 如果 n2.type 的值是字符串类型，则它描述的是普通标签元素
                if (typeof type === 'string') {
                    // 如果 n1 不存在，意味着挂载，则调用 mountElement 函数完成挂载
                    if (!n1) {
                        // 挂载时将锚点元素作为第三个参数传递给 mountElement 函数
                        mountElement(n2, container, anchor)
                    } else {
                        patchElement(n1, n2)
                    }
                } else if(type === Text){
                    
                    // 如果新 vnode 的类型是 Text，则说明该 vnode 描述的是文本节点
                    // 如果没有旧节点，则进行挂载
                    if (!n1) {
                        // 调用 createText 函数创建文本节点
                        const el = n2.el = createText(n2.children);
                        // 将文本节点插入到容器中
                        insert(el, container)
                    } else {
                        // 如果旧 vnode 存在，只需要使用新文本节点的文本内容更新旧文本节点即可
                        const el = n2.el = n1.el
                        if (n2.children !== n1.children) {
                            // 调用 setText 函数更新文本节点的内容
                            setText(el, n2.children)
                        }
                    }

                }else if(type === Comment){
                    if (!n1) {
                        const el = n2.el = createComment(n2.children);
                        insert(el, container)
                    } else {
                        const el = n2.el = n1.el
                        if (n2.children !== n1.children) {
                            setComment(el, n2.children)
                        }
                    }
                }else if(type === Fragment){
                    // 处理 Fragment 类型的 vnode
                    if (!n1) {
                        // 如果旧 vnode 不存在，则只需要将 Fragment 的 children 逐个挂载即可
                        n2.children.forEach(c => patch(null, c, container))
                    } else {
                        // 如果旧 vnode 存在，则只需要更新 Fragment 的 children 即可
                        patchChildren(n1, n2, container)
                    }

                } else if (
                    // type 是对象 --> 有状态组件
                    // type 是函数 --> 函数式组件
                    typeof type === 'object' || 
                    typeof type === 'function'
                ){ 
                    // vnode.type 的值是选项对象，作为组件来处理
                    if (!n1) {
                        // 挂载组件
                        mountComponent(n2, container, anchor)
                    } else {
                        // 更新组件
                        patchComponent(n1, n2, container)
                    }
                } else if (type === 'xxx') {
                    
                    // 处理其他类型的 vnode
                }
                
                
            }
            
            function patchElement(n1, n2) {
                const el = n2.el = n1.el
                const oldProps = n1.props
                const newProps = n2.props
                // 第一步：更新 props
                for (const key in newProps) {
                    if (newProps[key] !== oldProps[key]) {
                        // 注册新的 props
                        patchProps(el, key, oldProps[key], newProps[key])
                    }
                }
                for (const key in oldProps) {
                    if (!(key in newProps)) {
                        // 销毁旧的 props
                        patchProps(el, key, oldProps[key], null)
                    }
                }
                // 第二步：更新 children
                patchChildren(n1, n2, el)
            }
            
            /**
             * @param {VNode} n1 旧的 vnode
             * @param {VNode} n2 新的 vnode
             * @param {HTMLElement} container 容器
             * */ 
            function patchChildren(n1, n2, container) {
                // 判断新子节点的类型是否是文本节点
                if (typeof n2.children === 'string') {
                    // 旧子节点的类型有三种可能：没有子节点、文本子节点以及一组子节点
                    // 只有当旧子节点为一组子节点时，才需要逐个卸载，其他情况下什么都不需要做
                    if (Array.isArray(n1.children)) {
                        n1.children.forEach((c) => unmount(c))
                    }
                    // 最后将新的文本节点内容设置给容器元素
                    setElementText(container, n2.children)

                }else if (Array.isArray(n2.children)) {
                    // 说明新子节点是一组子节点
                    // 判断旧子节点是否也是一组子节点
                    if (Array.isArray(n1.children)) {
                        // 代码运行到这里，说明新旧子节点都是一组子节点
                        // 封装 patchKeyedChildren 函数处理两组子节点
                        
                        patchKeyedChildren(n1, n2, container)
                    } else {
                        // 此时：
                        // 旧子节点要么是文本子节点，要么不存在
                        // 但无论哪种情况，我们都只需要将容器清空，然后将新的一组子节点逐个挂载
                        setElementText(container, '')
                        n2.children.forEach(c => patch(null, c, container))
                    }
                }else{
                    // 代码运行到这里，说明新子节点不存在
                    // 旧子节点是一组子节点，只需逐个卸载即可
                    if (Array.isArray(n1.children)) {
                        n1.children.forEach(c => unmount(c))
                    } else if (typeof n1.children === 'string') {
                        // 旧子节点是文本子节点，清空内容即可
                        setElementText(container, '')
                    }
                    // 如果也没有旧子节点，那么什么都不需要做
                }
            }

            // 快速diff 处理dom
            function patchKeyedChildren(n1, n2, container){
                const oldChildren = n1.children
                const newChildren = n2.children
                // 处理相同的前置节点
                // 索引 j 指向新旧两组子节点的开头
                let j = 0
                let oldVNode = oldChildren[j]
                let newVNode = newChildren[j]
                // while 循环向后遍历，直到遇到拥有不同 key 值的节点为止
                // j<oldChildren.length&&j<newChildren.length 处理新旧子节点数量和顺序一致的情况
                while (oldVNode?.key === newVNode?.key&&j<oldChildren.length&&j<newChildren.length) {
                    
                    // 调用 patch 函数进行更新
                    patch(oldVNode, newVNode, container)
                    // 更新索引 j，让其递增
                    j++
                    oldVNode = oldChildren[j]
                    newVNode = newChildren[j]
                }

                // 更新相同的后置节点
                // 索引 oldEnd 指向旧的一组子节点的最后一个节点
                let oldEnd = oldChildren.length - 1
                // 索引 newEnd 指向新的一组子节点的最后一个节点
                let newEnd = newChildren.length - 1

                oldVNode = oldChildren[oldEnd]
                newVNode = newChildren[newEnd]

                // while 循环从后向前遍历，直到遇到拥有不同 key 值的节点为止
                // oldEnd>=j&&newEnd>=j 处理新旧子节点数量和顺序一致的情况
                while (oldVNode?.key === newVNode?.key&&oldEnd>=j&&newEnd>=j) {
                    // 调用 patch 函数进行更新
                    patch(oldVNode, newVNode, container)
                    // 递减 oldEnd 和 newEnd
                    oldEnd--
                    newEnd--
                    oldVNode = oldChildren[oldEnd]
                    newVNode = newChildren[newEnd]
                }

                
                // 预处理完毕后，如果满足如下条件，则说明从 j --> newEnd 之间的节点应作为新节点插入
                if (j > oldEnd && j <= newEnd) {
                    // 锚点的索引
                    const anchorIndex = newEnd + 1
                    // 锚点元素
                    const anchor = anchorIndex < newChildren.length ? newChildren[anchorIndex].el : null
                    // 采用 while 循环，调用 patch 函数逐个挂载新节点
                    while (j <= newEnd) {
                        patch(null, newChildren[j++], container, anchor)
                    }
                }else if (j > newEnd && j <= oldEnd) {
                    // j -> oldEnd 之间的节点应该被卸载
                    while (j <= oldEnd) {
                        unmount(oldChildren[j++])
                    }
                }else{
                    // 增加 else 分支来处理非理想情况
                    
                    // 新的一组子节点中剩余未处理节点的数量
                    const count = newEnd - j + 1
                    
                    // 构造 source 数组
                    // 储存 newChildren 中的节点在 oldChildren 中的位置索引
                    const source = new Array(count)
                    source.fill(-1)

                    // oldStart 和 newStart 分别为起始索引，即 j
                    const oldStart = j
                    const newStart = j

                    // 初始值为 false，代表是否需要移动节点
                    let moved = false
                    // 遍历旧的一组子节点的过程中遇到的最大索引值 k
                    let pos = 0
                    
                    // 构建索引表{ [newVNodeKey]: [newVNodeIndex] }
                    // 存储新的一组子节点的 key 和位置（index）索引之间的映射
                    const keyIndex = {}
                    for(let i = newStart; i <= newEnd; i++) {
                        keyIndex[newChildren[i].key] = i
                    }

                    // patched 变量，代表更新过的节点数量
                    let patched = 0
                    // 遍历旧的一组子节点中剩余未处理的节点
                    for(let i = oldStart; i <= oldEnd; i++) {
                        oldVNode = oldChildren[i]
                        // 如果更新过的节点数量小于等于需要更新的节点数量，则执行更新
                        if (patched <= count) {
                            // 通过索引表快速找到新的一组子节点中具有相同 key 值的节点位置
                            const k = keyIndex[oldVNode.key]
                            if (typeof k !== 'undefined') {
                                newVNode = newChildren[k]
                                // 调用 patch 函数完成更新
                                patch(oldVNode, newVNode, container)
                                // 每更新一个节点，都将 patched 变量 +1
                                patched++
                                // 填充 source 数组
                                source[k - newStart] = i
                                // 判断节点是否需要移动
                                if (k < pos) {
                                    moved = true
                                } else {
                                    pos = k
                                }
                            } else {
                                // 没找到
                                unmount(oldVNode)
                            }
                        }else{
                            // 如果更新过的节点数量大于需要更新的节点数量，则卸载多余的节点
                            unmount(oldVNode)
                        }
                    }

                    if(moved){
                        // 如果 moved 为真，则需要进行 DOM 移动操作

                        // 计算最长递增子序列
                        // 返回结果是最长递增子序列中的元素在 source 数组中的位置索引
                        const seq = getSequence(source)

                        console.log('seq', seq);

                        // s 指向最长递增子序列的最后一个元素
                        let s = seq.length - 1
                        // i 指向新的一组子节点的最后一个元素
                        let i = count - 1

                        // for 循环使得 i 递减，即按照图 11-24 中箭头的方向移动
                        for (i; i >= 0; i--) {
                            if (source[i] === -1) {
                                // 说明索引为 i 的节点是全新的节点，应该将其挂载
                                // 该节点在新 children 中的真实位置索引
                                const pos = i + newStart
                                const newVNode = newChildren[pos]
                                // 该节点的下一个节点的位置索引
                                const nextPos = pos + 1
                                // 锚点
                                const anchor = nextPos < newChildren.length
                                    ? newChildren[nextPos].el
                                    : null
                                // 挂载
                                patch(null, newVNode, container, anchor)
                            } else if (i !== seq[s]) {
                                // 如果节点的索引 i 不等于 seq[s] 的值，说明该节点需要移动
                                // 该节点在新的一组子节点中的真实位置索引
                                const pos = i + newStart
                                const newVNode = newChildren[pos]
                                // 该节点的下一个节点的位置索引
                                const nextPos = pos + 1
                                // 锚点
                                const anchor = nextPos < newChildren.length
                                    ? newChildren[nextPos].el
                                    : null
                                // 移动
                                insert(newVNode.el, container, anchor)
                            } else {
                                // 当 i === seq[s] 时，说明该位置的节点不需要移动
                                // 只需要让 s 指向下一个位置
                                s--
                            }
                        }
                    }
                    
                
                }

            }
            

            return {
                // render,
                // 作为 createRenderer 函数的返回值
                hydrate
            }
        }

        
        
        // 任务缓存队列，用一个 Set 数据结构来表示，这样就可以自动对任务进行去重
        const queue = new Set()
        // 一个标志，代表是否正在刷新任务队列
        let isFlushing = false
        // 创建一个立即 resolve 的 Promise 实例
        const p = Promise.resolve()

        // 调度器的主要函数，用来将一个任务添加到缓冲队列中，并开始刷新队列
        function queueJob(job){
            // 将 job 添加到任务队列 queue 中
            queue.add(job)
            // 如果还没有开始刷新队列，则刷新之
            if (!isFlushing) {
                // 将该标志设置为 true 以避免重复刷新
                isFlushing = true
                // 在微任务中刷新缓冲队列
                p.then(() => {
                    try {
                        // 执行任务队列中的任务
                        queue.forEach(job => job())
                    } finally {
                        // 重置状态
                        isFlushing = false
                        queue.clear()
                    }
                })
            }
        }
            

        // 组件
        const MyComponent = { // 新增 MyComponent
            name: 'App',
            setup() {
                const str = ref('foo')

                return () => {
                    return {
                        type: 'div',
                        children: [
                            {
                                type: 'span',
                                children: str.value,
                                props: {
                                    onClick: () => {
                                        str.value = 'bar'
                                    }
                                }
                            },
                            { type: 'span', children: 'baz' }
                        ]
                    }
                }
            }
        }

        // 创建渲染器
        const renderer = createRenderer({
            /**
             * 
             * 将属性设置相关操作封装到 patchProps 函数中，并作为渲染器选项传递
             * @param {HTMLElement} el dom 元素
             * @param {string} key 属性名
             * @param {any} prevValue 之前的值
             * @param {any} nextValue 新值
             * 
             * */ 
            patchProps(el, key, prevValue, nextValue) {
                // 匹配以 on 开头的属性，视其为事件
                if (/^on/.test(key)) {
                    // 定义 el._vei 为一个对象，存在事件名称到事件处理函数的映射
                    const invokers = el._vei || (el._vei = {})
                    //根据事件名称获取 invoker
                    let invoker = invokers[key]
                    // 根据属性名称得到对应的事件名称，例如 onClick ---> click
                    const name = key.slice(2).toLowerCase()
                    if(nextValue){
                        if (!invoker) {
                            // 如果没有 invoker，则将一个伪造的 invoker 处理函数缓存到 el._vei[key] 下，避免覆盖
                            // vei 是 vue event invoker 的首字母缩写
                            invoker = el._vei[key] = (e) => {
                                console.log('invoker', e);
                                // e.timeStamp 是事件发生的时间
                                // 如果事件发生的时间早于事件处理函数绑定的时间，则不执行事件处理函数
                                if (e.timeStamp < invoker.attached) return
                                // 如果 invoker.value 是数组，则遍历它并逐个调用事件处理函数
                                if (Array.isArray(invoker.value)) {
                                    invoker.value.forEach(fn => fn(e))
                                } else {
                                    // 否则直接作为函数调用
                                    // 当伪造的事件处理函数执行时，会执行真正的事件处理函数
                                    invoker.value(e)
                                }
                            }
                            // 将真正的事件处理函数赋值给 invoker.value
                            invoker.value = nextValue
                            // 添加 invoker.attached 属性，存储事件处理函数被绑定的时间
                            invoker.attached = performance.now()
                            // 绑定 invoker 作为事件处理函数
                            el.addEventListener(name, invoker)
                        } else {
                            // 如果 invoker 存在，意味着更新，并且只需要更新 invoker.value 的值即可
                            invoker.value = nextValue
                        }
                    } else if (invoker) {
                        // 新的事件绑定函数不存在，且之前绑定的 invoker 存在，则移除绑定
                        el.removeEventListener(name, invoker)
                    }
                } else if (key === 'class') {
                    // 对 class 进行特殊处理
                    el.className = nextValue || ''
                } else if (shouldSetAsProps(el, key, nextValue)) {
                    const type = typeof el[key]
                    if (type === 'boolean' && nextValue === '') {
                        el[key] = true
                    } else {
                        el[key] = nextValue
                    }
                } else {
                    el.setAttribute(key, nextValue)
                }
            },
            
            // 用于设置元素的文本节点
            setElementText(el, text) {
                el.textContent = text
            },
        })
        

        // 用来描述组件的 VNode 对象
        const CompVNode = {
            type: MyComponent,
        }

        // html 代表由服务端渲染的字符串
        const html = renderComponentVNode(CompVNode)

        // 假设客户端已经拿到了由服务端渲染的字符串
        // 获取挂载点
        const container = document.querySelector('#app')
        // 设置挂载点的 innerHTML，模拟由服务端渲染的内容
        container.innerHTML = html
        
        // 接着调用 hydrate 函数完成激活
        renderer.hydrate(CompVNode, container) // 新增
        
        // 测试
        console.log(renderComponentVNode(CompVNode))
    </script>
</html>